/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: MethodGenerator.java,v 1.2.4.1 2005/09/05 11:16:47 pvedula Exp $
 */

package com.sun.org.apache.xalan.internal.xsltc.compiler.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;


import com.sun.org.apache.bcel.internal.Constants;
import com.sun.org.apache.bcel.internal.classfile.Field;
import com.sun.org.apache.bcel.internal.classfile.Method;
import com.sun.org.apache.bcel.internal.generic.ALOAD;
import com.sun.org.apache.bcel.internal.generic.ASTORE;
import com.sun.org.apache.bcel.internal.generic.BranchHandle;
import com.sun.org.apache.bcel.internal.generic.BranchInstruction;
import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
import com.sun.org.apache.bcel.internal.generic.DLOAD;
import com.sun.org.apache.bcel.internal.generic.DSTORE;
import com.sun.org.apache.bcel.internal.generic.FLOAD;
import com.sun.org.apache.bcel.internal.generic.FSTORE;
import com.sun.org.apache.bcel.internal.generic.GETFIELD;
import com.sun.org.apache.bcel.internal.generic.GOTO;
import com.sun.org.apache.bcel.internal.generic.ICONST;
import com.sun.org.apache.bcel.internal.generic.IfInstruction;
import com.sun.org.apache.bcel.internal.generic.ILOAD;
import com.sun.org.apache.bcel.internal.generic.IndexedInstruction;
import com.sun.org.apache.bcel.internal.generic.INVOKEINTERFACE;
import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL;
import com.sun.org.apache.bcel.internal.generic.INVOKESTATIC;
import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
import com.sun.org.apache.bcel.internal.generic.ISTORE;
import com.sun.org.apache.bcel.internal.generic.Instruction;
import com.sun.org.apache.bcel.internal.generic.InstructionConstants;
import com.sun.org.apache.bcel.internal.generic.InstructionHandle;
import com.sun.org.apache.bcel.internal.generic.InstructionList;
import com.sun.org.apache.bcel.internal.generic.InstructionTargeter;
import com.sun.org.apache.bcel.internal.generic.LocalVariableGen;
import com.sun.org.apache.bcel.internal.generic.LocalVariableInstruction;
import com.sun.org.apache.bcel.internal.generic.LLOAD;
import com.sun.org.apache.bcel.internal.generic.LSTORE;
import com.sun.org.apache.bcel.internal.generic.MethodGen;
import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.org.apache.bcel.internal.generic.PUTFIELD;
import com.sun.org.apache.bcel.internal.generic.RET;
import com.sun.org.apache.bcel.internal.generic.Select;
import com.sun.org.apache.bcel.internal.generic.TargetLostException;
import com.sun.org.apache.bcel.internal.generic.Type;

import com.sun.org.apache.xalan.internal.xsltc.compiler.Pattern;
import com.sun.org.apache.xalan.internal.xsltc.compiler.XSLTC;

/**
 * @author Jacek Ambroziak
 * @author Santiago Pericas-Geertsen
 */
public class MethodGenerator extends MethodGen
    implements com.sun.org.apache.xalan.internal.xsltc.compiler.Constants {

  protected static final int INVALID_INDEX = -1;

  private static final String START_ELEMENT_SIG
      = "(" + STRING_SIG + ")V";
  private static final String END_ELEMENT_SIG
      = START_ELEMENT_SIG;

  private InstructionList _mapTypeSub;

  private static final int DOM_INDEX = 1;
  private static final int ITERATOR_INDEX = 2;
  private static final int HANDLER_INDEX = 3;

  private static final int MAX_METHOD_SIZE = 65535;
  private static final int MAX_BRANCH_TARGET_OFFSET = 32767;
  private static final int MIN_BRANCH_TARGET_OFFSET = -32768;

  private static final int TARGET_METHOD_SIZE = 60000;
  private static final int MINIMUM_OUTLINEABLE_CHUNK_SIZE = 1000;

  private Instruction _iloadCurrent;
  private Instruction _istoreCurrent;
  private final Instruction _astoreHandler;
  private final Instruction _aloadHandler;
  private final Instruction _astoreIterator;
  private final Instruction _aloadIterator;
  private final Instruction _aloadDom;
  private final Instruction _astoreDom;

  private final Instruction _startElement;
  private final Instruction _endElement;
  private final Instruction _startDocument;
  private final Instruction _endDocument;
  private final Instruction _attribute;
  private final Instruction _uniqueAttribute;
  private final Instruction _namespace;

  private final Instruction _setStartNode;
  private final Instruction _reset;
  private final Instruction _nextNode;

  private SlotAllocator _slotAllocator;
  private boolean _allocatorInit = false;
  private LocalVariableRegistry _localVariableRegistry;
  /**
   * A mapping between patterns and instruction lists used by
   * test sequences to avoid compiling the same pattern multiple
   * times. Note that patterns whose kernels are "*", "node()"
   * and "@*" can between shared by test sequences.
   */
  private Hashtable _preCompiled = new Hashtable();


  public MethodGenerator(int access_flags, Type return_type,
      Type[] arg_types, String[] arg_names,
      String method_name, String class_name,
      InstructionList il, ConstantPoolGen cpg) {
    super(access_flags, return_type, arg_types, arg_names, method_name,
        class_name, il, cpg);

    _astoreHandler = new ASTORE(HANDLER_INDEX);
    _aloadHandler = new ALOAD(HANDLER_INDEX);
    _astoreIterator = new ASTORE(ITERATOR_INDEX);
    _aloadIterator = new ALOAD(ITERATOR_INDEX);
    _aloadDom = new ALOAD(DOM_INDEX);
    _astoreDom = new ASTORE(DOM_INDEX);

    final int startElement =
        cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
            "startElement",
            START_ELEMENT_SIG);
    _startElement = new INVOKEINTERFACE(startElement, 2);

    final int endElement =
        cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
            "endElement",
            END_ELEMENT_SIG);
    _endElement = new INVOKEINTERFACE(endElement, 2);

    final int attribute =
        cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
            "addAttribute",
            "("
                + STRING_SIG
                + STRING_SIG
                + ")V");
    _attribute = new INVOKEINTERFACE(attribute, 3);

    final int uniqueAttribute =
        cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
            "addUniqueAttribute",
            "("
                + STRING_SIG
                + STRING_SIG
                + "I)V");
    _uniqueAttribute = new INVOKEINTERFACE(uniqueAttribute, 4);

    final int namespace =
        cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
            "namespaceAfterStartElement",
            "("
                + STRING_SIG
                + STRING_SIG
                + ")V");
    _namespace = new INVOKEINTERFACE(namespace, 3);

    int index = cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
        "startDocument",
        "()V");
    _startDocument = new INVOKEINTERFACE(index, 1);

    index = cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE,
        "endDocument",
        "()V");
    _endDocument = new INVOKEINTERFACE(index, 1);

    index = cpg.addInterfaceMethodref(NODE_ITERATOR,
        SET_START_NODE,
        SET_START_NODE_SIG);
    _setStartNode = new INVOKEINTERFACE(index, 2);

    index = cpg.addInterfaceMethodref(NODE_ITERATOR,
        "reset", "()" + NODE_ITERATOR_SIG);
    _reset = new INVOKEINTERFACE(index, 1);

    index = cpg.addInterfaceMethodref(NODE_ITERATOR, NEXT, NEXT_SIG);
    _nextNode = new INVOKEINTERFACE(index, 1);

    _slotAllocator = new SlotAllocator();
    _slotAllocator.initialize(getLocalVariableRegistry().getLocals(false));
    _allocatorInit = true;
  }

  /**
   * Allocates a local variable. If the slot allocator has already been
   * initialized, then call addLocalVariable2() so that the new variable
   * is known to the allocator. Failing to do this may cause the allocator
   * to return a slot that is already in use.
   */
  public LocalVariableGen addLocalVariable(String name, Type type,
      InstructionHandle start,
      InstructionHandle end) {
    LocalVariableGen lvg;

    if (_allocatorInit) {
      lvg = addLocalVariable2(name, type, start);
    } else {
      lvg = super.addLocalVariable(name, type, start, end);
      getLocalVariableRegistry().registerLocalVariable(lvg);
    }
    return lvg;
  }

  public LocalVariableGen addLocalVariable2(String name, Type type,
      InstructionHandle start) {
    LocalVariableGen lvg = super.addLocalVariable(name, type,
        _slotAllocator.allocateSlot(type),
        start, null);
    getLocalVariableRegistry().registerLocalVariable(lvg);
    return lvg;
  }

  private LocalVariableRegistry getLocalVariableRegistry() {
    if (_localVariableRegistry == null) {
      _localVariableRegistry = new LocalVariableRegistry();
    }

    return _localVariableRegistry;
  }

  /**
   * Keeps track of all local variables used in the method.
   * <p>The
   * {@link MethodGen#addLocalVariable(String, Type, InstructionHandle, InstructionHandle)}</code>
   * and
   * {@link MethodGen#addLocalVariable(String, Type, int, InstructionHandle, InstructionHandle)}</code>
   * methods of {@link MethodGen} will only keep track of
   * {@link LocalVariableGen} object until it'ss removed by a call to
   * {@link MethodGen#removeLocalVariable(LocalVariableGen)}.</p>
   * <p>In order to support efficient copying of local variables to outlined
   * methods by
   * {@link #outline(InstructionHandle, InstructionHandle, String, ClassGenerator)},
   * this class keeps track of all local variables defined by the method.</p>
   */
  protected class LocalVariableRegistry {

    /**
     * <p>A <code>java.lang.ArrayList</code> of all
     * {@link LocalVariableGen}s created for this method, indexed by the
     * slot number of the local variable.  The JVM stack frame of local
     * variables is divided into "slots".  A single slot can be used to
     * store more than one variable in a method, without regard to type, so
     * long as the byte code keeps the ranges of the two disjoint.</p>
     * <p>If only one registration of use of a particular slot occurs, the
     * corresponding entry of <code>_variables</code> contains the
     * <code>LocalVariableGen</code>; if more than one occurs, the
     * corresponding entry contains all such <code>LocalVariableGen</code>s
     * registered for the same slot; and if none occurs, the entry will be
     * <code>null</code>.
     */
    protected ArrayList _variables = new ArrayList();

    /**
     * Maps a name to a {@link LocalVariableGen}
     */
    protected HashMap _nameToLVGMap = new HashMap();

    /**
     * Registers a {@link org.apache.bcel.generic.LocalVariableGen}
     * for this method.
     * <p><b>Preconditions:</b>
     * <ul>
     * <li>The range of instructions for <code>lvg</code> does not
     * overlap with the range of instructions for any
     * <code>LocalVariableGen</code> with the same slot index previously
     * registered for this method.  <b><em>(Unchecked.)</em></b></li>
     * </ul></p>
     *
     * @param lvg The variable to be registered
     */
    protected void registerLocalVariable(LocalVariableGen lvg) {
      int slot = lvg.getIndex();

      int registrySize = _variables.size();

      // If the LocalVariableGen uses a slot index beyond any previously
      // encountered, expand the _variables, padding with intervening null
      // entries as required.
      if (slot >= registrySize) {
        for (int i = registrySize; i < slot; i++) {
          _variables.add(null);
        }
        _variables.add(lvg);
      } else {
        // If the LocalVariableGen reuses a slot, make sure the entry
        // in _variables contains an ArrayList and add the newly
        // registered LocalVariableGen to the list.  If the entry in
        // _variables just contains null padding, store the
        // LocalVariableGen directly.
        Object localsInSlot = _variables.get(slot);
        if (localsInSlot != null) {
          if (localsInSlot instanceof LocalVariableGen) {
            ArrayList listOfLocalsInSlot = new ArrayList();
            listOfLocalsInSlot.add(localsInSlot);
            listOfLocalsInSlot.add(lvg);
            _variables.set(slot, listOfLocalsInSlot);
          } else {
            ((ArrayList) localsInSlot).add(lvg);
          }
        } else {
          _variables.set(slot, lvg);
        }
      }

      registerByName(lvg);
    }

    /**
     * <p>Find which {@link LocalVariableGen}, if any, is registered for a
     * particular JVM local stack frame slot at a particular position in the
     * byte code for the method.</p>
     * <p><b>Preconditions:</b>
     * <ul>
     * <li>The {@link InstructionList#setPositions()} has been called for
     * the {@link InstructionList} associated with this
     * {@link MethodGenerator}.</li>
     * </ul></p>
     *
     * @param slot the JVM local stack frame slot number
     * @param offset the position in the byte code
     * @return the <code>LocalVariableGen</code> for the local variable stored in the relevant slot
     * at the relevant offset; <code>null</code> if there is none.
     */
    protected LocalVariableGen lookupRegisteredLocalVariable(int slot,
        int offset) {
      Object localsInSlot = (_variables != null) ? _variables.get(slot)
          : null;

      // If this slot index was never used, _variables.get will return
      // null; if it was used once, it will return the LocalVariableGen;
      // more than once it will return an ArrayList of all the
      // LocalVariableGens for variables stored in that slot.  For each
      // LocalVariableGen, check whether its range includes the
      // specified offset, and return the first such encountered.
      if (localsInSlot != null) {
        if (localsInSlot instanceof LocalVariableGen) {
          LocalVariableGen lvg = (LocalVariableGen) localsInSlot;
          if (offsetInLocalVariableGenRange(lvg, offset)) {
            return lvg;
          }
        } else {
          ArrayList listOfLocalsInSlot = (ArrayList) localsInSlot;
          int size = listOfLocalsInSlot.size();

          for (int i = 0; i < size; i++) {
            LocalVariableGen lvg =
                (LocalVariableGen) listOfLocalsInSlot.get(i);
            if (offsetInLocalVariableGenRange(lvg, offset)) {
              return lvg;
            }
          }
        }
      }

      // No local variable stored in the specified slot at the specified
      return null;
    }

    /**
     * <p>Set up a mapping of the name of the specified
     * {@link LocalVariableGen} object to the <code>LocalVariableGen</code>
     * itself.</p>
     * <p>This is a bit of a hack.  XSLTC is relying on the fact that the
     * name that is being looked up won't be duplicated, which isn't
     * guaranteed.  It replaces code which used to call
     * {@link MethodGen#getLocalVariables()} and looped through the
     * <code>LocalVariableGen</code> objects it contained to find the one
     * with the specified name.  However, <code>getLocalVariables()</code>
     * has the side effect of setting the start and end for any
     * <code>LocalVariableGen</code> which did not already have them
     * set, which causes problems for outlining..</p>
     * <p>See also {@link #lookUpByName(String)} and
     * {@link #removeByNameTracking(LocalVariableGen)}</P
     *
     * @param lvg a <code>LocalVariableGen</code>
     */
    protected void registerByName(LocalVariableGen lvg) {
      Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName());

      if (duplicateNameEntry == null) {
        _nameToLVGMap.put(lvg.getName(), lvg);
      } else {
        ArrayList sameNameList;

        if (duplicateNameEntry instanceof ArrayList) {
          sameNameList = (ArrayList) duplicateNameEntry;
          sameNameList.add(lvg);
        } else {
          sameNameList = new ArrayList();
          sameNameList.add(duplicateNameEntry);
          sameNameList.add(lvg);
        }

        _nameToLVGMap.put(lvg.getName(), sameNameList);
      }
    }

    /**
     * Remove the mapping from the name of the specified
     * {@link LocalVariableGen} to itself.
     * See also {@link #registerByName(LocalVariableGen)} and
     * {@link #lookUpByName(String)}
     *
     * @param lvg a <code>LocalVariableGen</code>
     */
    protected void removeByNameTracking(LocalVariableGen lvg) {
      Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName());

      if (duplicateNameEntry instanceof ArrayList) {
        ArrayList sameNameList = (ArrayList) duplicateNameEntry;
        for (int i = 0; i < sameNameList.size(); i++) {
          if (sameNameList.get(i) == lvg) {
            sameNameList.remove(i);
            break;
          }
        }
      } else {
        _nameToLVGMap.remove(lvg);
      }
    }

    /**
     * <p>Given the name of a variable, finds a {@link LocalVariableGen}
     * corresponding to it.</p>
     * <p>See also {@link #registerByName(LocalVariableGen)} and
     * {@link #removeByNameTracking(LocalVariableGen)}</p>
     */
    protected LocalVariableGen lookUpByName(String name) {
      LocalVariableGen lvg = null;
      Object duplicateNameEntry = _nameToLVGMap.get(name);

      if (duplicateNameEntry instanceof ArrayList) {
        ArrayList sameNameList = (ArrayList) duplicateNameEntry;

        for (int i = 0; i < sameNameList.size(); i++) {
          lvg = (LocalVariableGen) sameNameList.get(i);
          if (lvg.getName() == name) {
            break;
          }
        }
      } else {
        lvg = (LocalVariableGen) duplicateNameEntry;
      }

      return lvg;
    }

    /**
     * <p>Gets all {@link LocalVariableGen} objects for this method.</p>
     * <p>When the <code>includeRemoved</code> argument has the value
     * <code>false</code>, this method replaces uses of
     * {@link MethodGen#getLocalVariables()} which has
     * a side-effect of setting the start and end range for any
     * <code>LocalVariableGen</code> if either was <code>null</code>.  That
     * side-effect causes problems for outlining of code in XSLTC.
     *
     * @param includeRemoved Specifies whether all local variables ever declared should be returned
     * (<code>true</code>) or only those not removed (<code>false</code>)
     * @return an array of <code>LocalVariableGen</code> containing all the local variables
     */
    protected LocalVariableGen[] getLocals(boolean includeRemoved) {
      LocalVariableGen[] locals = null;
      ArrayList allVarsEverDeclared = new ArrayList();

      if (includeRemoved) {
        int slotCount = allVarsEverDeclared.size();

        for (int i = 0; i < slotCount; i++) {
          Object slotEntries = _variables.get(i);
          if (slotEntries != null) {
            if (slotEntries instanceof ArrayList) {
              ArrayList slotList = (ArrayList) slotEntries;

              for (int j = 0; j < slotList.size(); j++) {
                allVarsEverDeclared.add(slotList.get(i));
              }
            } else {
              allVarsEverDeclared.add(slotEntries);
            }
          }
        }
      } else {
        Iterator nameVarsPairsIter = _nameToLVGMap.entrySet().iterator();

        while (nameVarsPairsIter.hasNext()) {
          Map.Entry nameVarsPair =
              (Map.Entry) nameVarsPairsIter.next();
          Object vars = nameVarsPair.getValue();
          if (vars != null) {
            if (vars instanceof ArrayList) {
              ArrayList varsList = (ArrayList) vars;
              for (int i = 0; i < varsList.size(); i++) {
                allVarsEverDeclared.add(varsList.get(i));
              }
            } else {
              allVarsEverDeclared.add(vars);
            }
          }
        }
      }

      locals = new LocalVariableGen[allVarsEverDeclared.size()];
      allVarsEverDeclared.toArray(locals);

      return locals;
    }
  }

  /**
   * Determines whether a particular variable is in use at a particular offset
   * in the byte code for this method.
   * <p><b>Preconditions:</b>
   * <ul>
   * <li>The {@link InstructionList#setPositions()} has been called for the
   * {@link InstructionList} associated with this {@link MethodGenerator}.
   * </li></ul></p>
   *
   * @param lvg the {@link LocalVariableGen} for the variable
   * @param offset the position in the byte code
   * @return <code>true</code> if and only if the specified variable is in use at the particular
   * byte code offset.
   */
  boolean offsetInLocalVariableGenRange(LocalVariableGen lvg, int offset) {
    InstructionHandle lvgStart = lvg.getStart();
    InstructionHandle lvgEnd = lvg.getEnd();

    // If no start handle is recorded for the LocalVariableGen, it is
    // assumed to be in use from the beginning of the method.
    if (lvgStart == null) {
      lvgStart = getInstructionList().getStart();
    }

    // If no end handle is recorded for the LocalVariableGen, it is assumed
    // to be in use to the end of the method.
    if (lvgEnd == null) {
      lvgEnd = getInstructionList().getEnd();
    }

    // Does the range of the instruction include the specified offset?
    // Note that the InstructionHandle.getPosition method returns the
    // offset of the beginning of an instruction.  A LocalVariableGen's
    // range includes the end instruction itself, so that instruction's
    // length must be taken into consideration in computing whether the
    // varible is in range at a particular offset.
    return ((lvgStart.getPosition() <= offset)
        && (lvgEnd.getPosition()
        + lvgEnd.getInstruction().getLength() >= offset));
  }

  public void removeLocalVariable(LocalVariableGen lvg) {
    _slotAllocator.releaseSlot(lvg);
    getLocalVariableRegistry().removeByNameTracking(lvg);
    super.removeLocalVariable(lvg);
  }

  public Instruction loadDOM() {
    return _aloadDom;
  }

  public Instruction storeDOM() {
    return _astoreDom;
  }

  public Instruction storeHandler() {
    return _astoreHandler;
  }

  public Instruction loadHandler() {
    return _aloadHandler;
  }

  public Instruction storeIterator() {
    return _astoreIterator;
  }

  public Instruction loadIterator() {
    return _aloadIterator;
  }

  public final Instruction setStartNode() {
    return _setStartNode;
  }

  public final Instruction reset() {
    return _reset;
  }

  public final Instruction nextNode() {
    return _nextNode;
  }

  public final Instruction startElement() {
    return _startElement;
  }

  public final Instruction endElement() {
    return _endElement;
  }

  public final Instruction startDocument() {
    return _startDocument;
  }

  public final Instruction endDocument() {
    return _endDocument;
  }

  public final Instruction attribute() {
    return _attribute;
  }

  public final Instruction uniqueAttribute() {
    return _uniqueAttribute;
  }

  public final Instruction namespace() {
    return _namespace;
  }

  public Instruction loadCurrentNode() {
    if (_iloadCurrent == null) {
      int idx = getLocalIndex("current");
      if (idx > 0) {
        _iloadCurrent = new ILOAD(idx);
      } else {
        _iloadCurrent = new ICONST(0);
      }
    }
    return _iloadCurrent;
  }

  public Instruction storeCurrentNode() {
    return _istoreCurrent != null
        ? _istoreCurrent
        : (_istoreCurrent = new ISTORE(getLocalIndex("current")));
  }

  /**
   * by default context node is the same as current node. MK437
   */
  public Instruction loadContextNode() {
    return loadCurrentNode();
  }

  public Instruction storeContextNode() {
    return storeCurrentNode();
  }

  public int getLocalIndex(String name) {
    return getLocalVariable(name).getIndex();
  }

  public LocalVariableGen getLocalVariable(String name) {
    return getLocalVariableRegistry().lookUpByName(name);
  }

  public void setMaxLocals() {

    // Get the current number of local variable slots
    int maxLocals = super.getMaxLocals();
    int prevLocals = maxLocals;

    // Get numer of actual variables
    final LocalVariableGen[] localVars = super.getLocalVariables();
    if (localVars != null) {
      if (localVars.length > maxLocals) {
        maxLocals = localVars.length;
      }
    }

    // We want at least 5 local variable slots (for parameters)
    if (maxLocals < 5) {
      maxLocals = 5;
    }

    super.setMaxLocals(maxLocals);
  }

  /**
   * Add a pre-compiled pattern to this mode.
   */
  public void addInstructionList(Pattern pattern, InstructionList ilist) {
    _preCompiled.put(pattern, ilist);
  }

  /**
   * Get the instruction list for a pre-compiled pattern. Used by
   * test sequences to avoid compiling patterns more than once.
   */
  public InstructionList getInstructionList(Pattern pattern) {
    return (InstructionList) _preCompiled.get(pattern);
  }

  /**
   * Used to keep track of an outlineable chunk of instructions in the
   * current method.  See {@link OutlineableChunkStart} and
   * {@link OutlineableChunkEnd} for more information.
   */
  private class Chunk implements Comparable {

    /**
     * {@link InstructionHandle} of the first instruction in the outlineable
     * chunk.
     */
    private InstructionHandle m_start;

    /**
     * {@link org.apache.bcel.generic.InstructionHandle} of the first
     * instruction in the outlineable chunk.
     */
    private InstructionHandle m_end;

    /**
     * Number of bytes in the instructions contained in this outlineable
     * chunk.
     */
    private int m_size;

    /**
     * <p>Constructor for an outlineable {@link MethodGenerator.Chunk}.</p>
     * <p><b>Preconditions:</b>
     * <ul>
     * <li>The {@link InstructionList#setPositions()} has been called for
     * the {@link InstructionList} associated with this
     * {@link MethodGenerator}.</li>
     * </ul></p>
     *
     * @param start The {@link InstructionHandle} of the first instruction in the outlineable
     * chunk.
     * @param end The {@link InstructionHandle} of the last instruction in the outlineable chunk.
     */
    Chunk(InstructionHandle start, InstructionHandle end) {
      m_start = start;
      m_end = end;
      m_size = end.getPosition() - start.getPosition();
    }

    /**
     * Determines whether this outlineable {@link MethodGenerator.Chunk} is
     * followed immediately by the argument
     * <code>MethodGenerator.Chunk</code>, with no other intervening
     * instructions, including {@link OutlineableChunkStart} or
     * {@link OutlineableChunkEnd} instructions.
     *
     * @param neighbour an outlineable {@link MethodGenerator.Chunk}
     * @return <code>true</code> if and only if the argument chunk immediately follows
     * <code>this</code> chunk
     */
    boolean isAdjacentTo(Chunk neighbour) {
      return getChunkEnd().getNext() == neighbour.getChunkStart();
    }

    /**
     * Getter method for the start of this {@linke MethodGenerator.Chunk}
     *
     * @return the {@link org.apache.bcel.generic.InstructionHandle} of the start of this chunk
     */
    InstructionHandle getChunkStart() {
      return m_start;
    }

    /**
     * Getter method for the end of this {@link MethodGenerator.Chunk}
     *
     * @return the {@link InstructionHandle} of the start of this chunk
     */
    InstructionHandle getChunkEnd() {
      return m_end;
    }

    /**
     * The size of this {@link MethodGenerator.Chunk}
     *
     * @return the number of bytes in the byte code represented by this chunk.
     */
    int getChunkSize() {
      return m_size;
    }

    /**
     * Implements the <code>java.util.Comparable.compareTo(Object)</code>
     * method.
     *
     * @return <ul> <li>A positive <code>int</code> if the length of <code>this</code> chunk in
     * bytes is greater than that of <code>comparand</code></li> <li>A negative <code>int</code> if
     * the length of <code>this</code> chunk in bytes is less than that of
     * <code>comparand</code></li> <li>Zero, otherwise.</li> </ul>
     */
    public int compareTo(Object comparand) {
      return getChunkSize() - ((Chunk) comparand).getChunkSize();
    }
  }

  /**
   * Find the outlineable chunks in this method that would be the best choices
   * to outline, based on size and position in the method.
   *
   * @param classGen The {@link ClassGen} with which the generated methods will be associated
   * @param totalMethodSize the size of the bytecode in the original method
   * @return a <code>java.util.ArrayList</code> containing the {@link MethodGenerator.Chunk}s that
   * may be outlined from this method
   */
  private ArrayList getCandidateChunks(ClassGenerator classGen,
      int totalMethodSize) {
    Iterator instructions = getInstructionList().iterator();
    ArrayList candidateChunks = new ArrayList();
    ArrayList currLevelChunks = new ArrayList();
    Stack subChunkStack = new Stack();
    boolean openChunkAtCurrLevel = false;
    boolean firstInstruction = true;

    InstructionHandle currentHandle;

    if (m_openChunks != 0) {
      String msg =
          (new ErrorMsg(ErrorMsg.OUTLINE_ERR_UNBALANCED_MARKERS))
              .toString();
      throw new InternalError(msg);
    }

    // Scan instructions in the method, keeping track of the nesting level
    // of outlineable chunks.
    //
    // currLevelChunks
    //     keeps track of the child chunks of a chunk.  For each chunk,
    //     there will be a pair of entries:  the InstructionHandles for the
    //     start and for the end of the chunk
    // subChunkStack
    //     a stack containing the partially accumulated currLevelChunks for
    //     each chunk that's still open at the current position in the
    //     InstructionList.
    // candidateChunks
    //     the list of chunks which have been accepted as candidates chunks
    //     for outlining
    do {
      // Get the next instruction.  The loop will perform one extra
      // iteration after it reaches the end of the InstructionList, with
      // currentHandle set to null.
      currentHandle = instructions.hasNext()
          ? (InstructionHandle) instructions.next()
          : null;
      Instruction inst =
          (currentHandle != null) ? currentHandle.getInstruction()
              : null;

      // At the first iteration, create a chunk representing all the
      // code in the method.  This is done just to simplify the logic -
      // this chunk can never be outlined because it will be too big.
      if (firstInstruction) {
        openChunkAtCurrLevel = true;
        currLevelChunks.add(currentHandle);
        firstInstruction = false;
      }

      // Found a new chunk
      if (inst instanceof OutlineableChunkStart) {
        // If last MarkerInstruction encountered was an
        // OutlineableChunkStart, this represents the first chunk
        // nested within that previous chunk - push the list of chunks
        // from the outer level onto the stack
        if (openChunkAtCurrLevel) {
          subChunkStack.push(currLevelChunks);
          currLevelChunks = new ArrayList();
        }

        openChunkAtCurrLevel = true;
        currLevelChunks.add(currentHandle);
        // Close off an open chunk
      } else if (currentHandle == null
          || inst instanceof OutlineableChunkEnd) {
        ArrayList nestedSubChunks = null;

        // If the last MarkerInstruction encountered was an
        // OutlineableChunkEnd, it means that the current instruction
        // marks the end of a chunk that contained child chunks.
        // Those children might need to be examined below in case they
        // are better candidates for outlining than the current chunk.
        if (!openChunkAtCurrLevel) {
          nestedSubChunks = currLevelChunks;
          currLevelChunks = (ArrayList) subChunkStack.pop();
        }

        // Get the handle for the start of this chunk (the last entry
        // in currLevelChunks)
        InstructionHandle chunkStart =
            (InstructionHandle) currLevelChunks.get(
                currLevelChunks.size() - 1);

        int chunkEndPosition =
            (currentHandle != null) ? currentHandle.getPosition()
                : totalMethodSize;
        int chunkSize = chunkEndPosition - chunkStart.getPosition();

        // Two ranges of chunk size to consider:
        //
        // 1. [0,TARGET_METHOD_SIZE]
        //      Keep this chunk in consideration as a candidate,
        //      and ignore its subchunks, if any - there's nothing to be
        //      gained by outlining both the current chunk and its
        //      children!
        //
        // 2. (TARGET_METHOD_SIZE,+infinity)
        //      Ignore this chunk - it's too big.  Add its subchunks
        //      as candidates, after merging adjacent chunks to produce
        //      chunks that are as large as possible
        if (chunkSize <= TARGET_METHOD_SIZE) {
          currLevelChunks.add(currentHandle);
        } else {
          if (!openChunkAtCurrLevel) {
            int childChunkCount = nestedSubChunks.size() / 2;
            if (childChunkCount > 0) {
              Chunk[] childChunks = new Chunk[childChunkCount];

              // Gather all the child chunks of the current chunk
              for (int i = 0; i < childChunkCount; i++) {
                InstructionHandle start =
                    (InstructionHandle) nestedSubChunks
                        .get(i * 2);
                InstructionHandle end =
                    (InstructionHandle) nestedSubChunks
                        .get(i * 2 + 1);

                childChunks[i] = new Chunk(start, end);
              }

              // Merge adjacent siblings
              ArrayList mergedChildChunks =
                  mergeAdjacentChunks(childChunks);

              // Add chunks that mean minimum size requirements
              // to the list of candidate chunks for outlining
              for (int i = 0; i < mergedChildChunks.size(); i++) {
                Chunk mergedChunk =
                    (Chunk) mergedChildChunks.get(i);
                int mergedSize = mergedChunk.getChunkSize();

                if (mergedSize >= MINIMUM_OUTLINEABLE_CHUNK_SIZE
                    && mergedSize <= TARGET_METHOD_SIZE) {
                  candidateChunks.add(mergedChunk);
                }
              }
            }
          }

          // Drop the chunk which was too big
          currLevelChunks.remove(currLevelChunks.size() - 1);
        }

        // currLevelChunks contains pairs of InstructionHandles.  If
        // its size is an odd number, the loop has encountered the
        // start of a chunk at this level, but not its end.
        openChunkAtCurrLevel = ((currLevelChunks.size() & 0x1) == 1);
      }

    } while (currentHandle != null);

    return candidateChunks;
  }

  /**
   * Merge adjacent sibling chunks to produce larger candidate chunks for
   * outlining
   *
   * @param chunks array of sibling {@link MethodGenerator.Chunk}s that are under consideration for
   * outlining.  Chunks must be in the order encountered in the {@link InstructionList}
   * @return a <code>java.util.ArrayList</code> of <code>MethodGenerator.Chunk</code>s maximally
   * merged
   */
  private ArrayList mergeAdjacentChunks(Chunk[] chunks) {
    int[] adjacencyRunStart = new int[chunks.length];
    int[] adjacencyRunLength = new int[chunks.length];
    boolean[] chunkWasMerged = new boolean[chunks.length];

    int maximumRunOfChunks = 0;
    int startOfCurrentRun;
    int numAdjacentRuns = 0;

    ArrayList mergedChunks = new ArrayList();

    startOfCurrentRun = 0;

    // Loop through chunks, and record in adjacencyRunStart where each
    // run of adjacent chunks begins and how many are in that run.  For
    // example, given chunks A B C D E F, if A is adjacent to B, but not
    // to C, and C, D, E and F are all adjacent,
    //   adjacencyRunStart[0] == 0; adjacencyRunLength[0] == 2
    //   adjacencyRunStart[1] == 2; adjacencyRunLength[1] == 4
    for (int i = 1; i < chunks.length; i++) {
      if (!chunks[i - 1].isAdjacentTo(chunks[i])) {
        int lengthOfRun = i - startOfCurrentRun;

        // Track the longest run of chunks found
        if (maximumRunOfChunks < lengthOfRun) {
          maximumRunOfChunks = lengthOfRun;
        }

        if (lengthOfRun > 1) {
          adjacencyRunLength[numAdjacentRuns] = lengthOfRun;
          adjacencyRunStart[numAdjacentRuns] = startOfCurrentRun;
          numAdjacentRuns++;
        }

        startOfCurrentRun = i;
      }
    }

    if (chunks.length - startOfCurrentRun > 1) {
      int lengthOfRun = chunks.length - startOfCurrentRun;

      // Track the longest run of chunks found
      if (maximumRunOfChunks < lengthOfRun) {
        maximumRunOfChunks = lengthOfRun;
      }

      adjacencyRunLength[numAdjacentRuns] =
          chunks.length - startOfCurrentRun;
      adjacencyRunStart[numAdjacentRuns] = startOfCurrentRun;
      numAdjacentRuns++;
    }

    // Try merging adjacent chunks to come up with better sized chunks for
    // outlining.  This algorithm is not optimal, but it should be
    // reasonably fast.  Consider an example like this, where four chunks
    // of the sizes specified in brackets are adjacent.  The best way of
    // combining these chunks would be to merge the first pair and merge
    // the last three to form two chunks, but the algorithm will merge the
    // three in the middle instead, leaving three chunks in all.
    //    [25000] [25000] [20000] [1000] [20000]

    // Start by trying to merge the maximum number of adjacent chunks, and
    // work down from there.
    for (int numToMerge = maximumRunOfChunks; numToMerge > 1; numToMerge--) {
      // Look at each run of adjacent chunks
      for (int run = 0; run < numAdjacentRuns; run++) {
        int runStart = adjacencyRunStart[run];
        int runEnd = runStart + adjacencyRunLength[run] - 1;

        boolean foundChunksToMerge = false;

        // Within the current run of adjacent chunks, look at all
        // "subruns" of length numToMerge, until we run out or find
        // a subrun that can be merged.
        for (int mergeStart = runStart;
            mergeStart + numToMerge - 1 <= runEnd && !foundChunksToMerge;
            mergeStart++) {
          int mergeEnd = mergeStart + numToMerge - 1;
          int mergeSize = 0;

          // Find out how big the subrun is
          for (int j = mergeStart; j <= mergeEnd; j++) {
            mergeSize = mergeSize + chunks[j].getChunkSize();
          }

          // If the current subrun is small enough to outline,
          // merge it, and split the remaining chunks in the run
          if (mergeSize <= TARGET_METHOD_SIZE) {
            foundChunksToMerge = true;

            for (int j = mergeStart; j <= mergeEnd; j++) {
              chunkWasMerged[j] = true;
            }

            mergedChunks.add(
                new Chunk(chunks[mergeStart].getChunkStart(),
                    chunks[mergeEnd].getChunkEnd()));

            // Adjust the length of the current run of adjacent
            // chunks to end at the newly merged chunk...
            adjacencyRunLength[run] =
                adjacencyRunStart[run] - mergeStart;

            int trailingRunLength = runEnd - mergeEnd;

            // and any chunks that follow the newly merged chunk
            // in the current run of adjacent chunks form another
            // new run of adjacent chunks
            if (trailingRunLength >= 2) {
              adjacencyRunStart[numAdjacentRuns] = mergeEnd + 1;
              adjacencyRunLength[numAdjacentRuns] =
                  trailingRunLength;
              numAdjacentRuns++;
            }
          }
        }
      }
    }

    // Make a final pass for any chunk that wasn't merged with a sibling
    // and include it in the list of chunks after merging.
    for (int i = 0; i < chunks.length; i++) {
      if (!chunkWasMerged[i]) {
        mergedChunks.add(chunks[i]);
      }
    }

    return mergedChunks;
  }

  /**
   * Breaks up the IL for this {@link MethodGenerator} into separate
   * outlined methods so that no method exceeds the 64KB limit on the length
   * of the byte code associated with a method.
   *
   * @param classGen The {@link ClassGen} with which the generated methods will be associated
   * @param originalMethodSize The number of bytes of bytecode represented by the {@link
   * InstructionList} of this method
   * @return an array of the outlined <code>Method</code>s and the original method itself
   */
  public Method[] outlineChunks(ClassGenerator classGen,
      int originalMethodSize) {
    ArrayList methodsOutlined = new ArrayList();
    int currentMethodSize = originalMethodSize;

    int outlinedCount = 0;
    boolean moreMethodsOutlined;
    String originalMethodName = getName();

    // Special handling for initialization methods.  No other methods can
    // include the less than and greater than characters in their names,
    // so we munge the names here.
    if (originalMethodName.equals("<init>")) {
      originalMethodName = "$lt$init$gt$";
    } else if (originalMethodName.equals("<clinit>")) {
      originalMethodName = "$lt$clinit$gt$";
    }

    // Loop until the original method comes in under the JVM limit or
    // the loop was unable to outline any more methods
    do {
      // Get all the best candidates for outlining, and sort them in
      // ascending order of size
      ArrayList candidateChunks = getCandidateChunks(classGen,
          currentMethodSize);
      Collections.sort(candidateChunks);

      moreMethodsOutlined = false;

      // Loop over the candidates for outlining, from the largest to the
      // smallest and outline them one at a time, until the loop has
      // outlined all or the original method comes in under the JVM
      // limit on the size of a method.
      for (int i = candidateChunks.size() - 1;
          i >= 0 && currentMethodSize > TARGET_METHOD_SIZE;
          i--) {
        Chunk chunkToOutline = (Chunk) candidateChunks.get(i);

        methodsOutlined.add(outline(chunkToOutline.getChunkStart(),
            chunkToOutline.getChunkEnd(),
            originalMethodName + "$outline$"
                + outlinedCount,
            classGen));
        outlinedCount++;
        moreMethodsOutlined = true;

        InstructionList il = getInstructionList();
        InstructionHandle lastInst = il.getEnd();
        il.setPositions();

        // Check the size of the method now
        currentMethodSize =
            lastInst.getPosition()
                + lastInst.getInstruction().getLength();
      }
    } while (moreMethodsOutlined && currentMethodSize > TARGET_METHOD_SIZE);

    // Outlining failed to reduce the size of the current method
    // sufficiently.  Throw an internal error.
    if (currentMethodSize > MAX_METHOD_SIZE) {
      String msg = (new ErrorMsg(ErrorMsg.OUTLINE_ERR_METHOD_TOO_BIG))
          .toString();
      throw new InternalError(msg);
    }

    Method[] methodsArr = new Method[methodsOutlined.size() + 1];
    methodsOutlined.toArray(methodsArr);

    methodsArr[methodsOutlined.size()] = getThisMethod();

    return methodsArr;
  }

  /**
   * Given an outlineable chunk of code in the current {@link MethodGenerator}
   * move ("outline") the chunk to a new method, and replace the chunk in the
   * old method with a reference to that new method.  No
   * {@link OutlineableChunkStart} or {@link OutlineableChunkEnd} instructions
   * are copied.
   *
   * @param first The {@link InstructionHandle} of the first instruction in the chunk to outline
   * @param last The <code>InstructionHandle</code> of the last instruction in the chunk to outline
   * @param outlinedMethodName The name of the new method
   * @param classGen The {@link ClassGenerator} of which the original and new methods will be
   * members
   * @return The new {@link Method} containing the outlined code.
   */
  private Method outline(InstructionHandle first, InstructionHandle last,
      String outlinedMethodName, ClassGenerator classGen) {
    // We're not equipped to deal with exception handlers yet.  Bail out!
    if (getExceptionHandlers().length != 0) {
      String msg = (new ErrorMsg(ErrorMsg.OUTLINE_ERR_TRY_CATCH))
          .toString();
      throw new InternalError(msg);
    }

    int outlineChunkStartOffset = first.getPosition();
    int outlineChunkEndOffset = last.getPosition()
        + last.getInstruction().getLength();

    ConstantPoolGen cpg = getConstantPool();

    // Create new outlined method with signature:
    //
    //   private final outlinedMethodName(CopyLocals copyLocals);
    //
    // CopyLocals is an object that is used to copy-in/copy-out local
    // variables that are used by the outlined method.   Only locals whose
    // value is potentially set or referenced outside the range of the
    // chunk that is being outlined will be represented in CopyLocals.  The
    // type of the variable for copying local variables is actually
    // generated to be unique - it is not named CopyLocals.
    //
    // The outlined method never needs to be referenced outside of this
    // class, and will never be overridden, so we mark it private final.
    final InstructionList newIL = new InstructionList();

    final XSLTC xsltc = classGen.getParser().getXSLTC();
    final String argTypeName = xsltc.getHelperClassName();
    final Type[] argTypes =
        new Type[]{(new ObjectType(argTypeName)).toJCType()};
    final String argName = "copyLocals";
    final String[] argNames = new String[]{argName};

    int methodAttributes = ACC_PRIVATE | ACC_FINAL;
    final boolean isStaticMethod = (getAccessFlags() & ACC_STATIC) != 0;

    if (isStaticMethod) {
      methodAttributes = methodAttributes | ACC_STATIC;
    }

    final MethodGenerator outlinedMethodGen =
        new MethodGenerator(methodAttributes,
            com.sun.org.apache.bcel.internal.generic.Type.VOID,
            argTypes, argNames, outlinedMethodName,
            getClassName(), newIL, cpg);

    // Create class for copying local variables to the outlined method.
    // The fields the class will need to contain will be determined as the
    // code in the outlineable chunk is examined.
    ClassGenerator copyAreaCG
        = new ClassGenerator(argTypeName, OBJECT_CLASS, argTypeName + ".java",
        ACC_FINAL | ACC_PUBLIC | ACC_SUPER, null,
        classGen.getStylesheet()) {
      public boolean isExternal() {
        return true;
      }
    };
    ConstantPoolGen copyAreaCPG = copyAreaCG.getConstantPool();
    copyAreaCG.addEmptyConstructor(ACC_PUBLIC);

    // Number of fields in the copy class
    int copyAreaFieldCount = 0;

    // The handle for the instruction after the last one to be outlined.
    // Note that this should never end up being null.  An outlineable chunk
    // won't contain a RETURN instruction or other branch out of the chunk,
    // and the JVM specification prohibits code in a method from just
    // "falling off the end" so this should always point to a valid handle.
    InstructionHandle limit = last.getNext();

    // InstructionLists for copying values into and out of an instance of
    // CopyLocals:
    //      oldMethCoypInIL  - from locals in old method into an instance
    //                         of the CopyLocals class (oldMethCopyInIL)
    //      oldMethCopyOutIL - from CopyLocals back into locals in the old
    //                         method
    //      newMethCopyInIL  - from CopyLocals into locals in the new
    //                         method
    //      newMethCopyOutIL - from locals in new method into the instance
    //                         of the CopyLocals class
    InstructionList oldMethCopyInIL = new InstructionList();
    InstructionList oldMethCopyOutIL = new InstructionList();
    InstructionList newMethCopyInIL = new InstructionList();
    InstructionList newMethCopyOutIL = new InstructionList();

    // Allocate instance of class in which we'll copy in or copy out locals
    // and make two copies:  last copy is used to invoke constructor;
    // other two are used for references to fields in the CopyLocals object
    InstructionHandle outlinedMethodCallSetup =
        oldMethCopyInIL.append(new NEW(cpg.addClass(argTypeName)));
    oldMethCopyInIL.append(InstructionConstants.DUP);
    oldMethCopyInIL.append(InstructionConstants.DUP);
    oldMethCopyInIL.append(
        new INVOKESPECIAL(cpg.addMethodref(argTypeName, "<init>", "()V")));

    // Generate code to invoke the new outlined method, and place the code
    // on oldMethCopyOutIL
    InstructionHandle outlinedMethodRef;

    if (isStaticMethod) {
      outlinedMethodRef =
          oldMethCopyOutIL.append(
              new INVOKESTATIC(cpg.addMethodref(
                  classGen.getClassName(),
                  outlinedMethodName,
                  outlinedMethodGen.getSignature())));
    } else {
      oldMethCopyOutIL.append(InstructionConstants.THIS);
      oldMethCopyOutIL.append(InstructionConstants.SWAP);
      outlinedMethodRef =
          oldMethCopyOutIL.append(
              new INVOKEVIRTUAL(cpg.addMethodref(
                  classGen.getClassName(),
                  outlinedMethodName,
                  outlinedMethodGen.getSignature())));
    }

    // Used to keep track of the first in a sequence of
    // OutlineableChunkStart instructions
    boolean chunkStartTargetMappingsPending = false;
    InstructionHandle pendingTargetMappingHandle = null;

    // Used to keep track of the last instruction that was copied
    InstructionHandle lastCopyHandle = null;

    // Keeps track of the mapping from instruction handles in the old
    // method to instruction handles in the outlined method.  Only need
    // to track instructions that are targeted by something else in the
    // generated BCEL
    HashMap targetMap = new HashMap();

    // Keeps track of the mapping from local variables in the old method
    // to local variables in the outlined method.
    HashMap localVarMap = new HashMap();

    HashMap revisedLocalVarStart = new HashMap();
    HashMap revisedLocalVarEnd = new HashMap();

    // Pass 1: Make copies of all instructions, append them to the new list
    // and associate old instruction references with the new ones, i.e.,
    // a 1:1 mapping.  The special marker instructions are not copied.
    // Also, identify local variables whose values need to be copied into or
    // out of the new outlined method, and builds up targetMap and
    // localVarMap as described above.  The code identifies those local
    // variables first so that they can have fixed slots in the stack
    // frame for the outlined method assigned them ahead of all those
    // variables that don't need to exist for the entirety of the outlined
    // method invocation.
    for (InstructionHandle ih = first; ih != limit; ih = ih.getNext()) {
      Instruction inst = ih.getInstruction();

      // MarkerInstructions are not copied, so if something else targets
      // one, the targetMap will point to the nearest copied sibling
      // InstructionHandle:  for an OutlineableChunkEnd, the nearest
      // preceding sibling; for an OutlineableChunkStart, the nearest
      // following sibling.
      if (inst instanceof MarkerInstruction) {
        if (ih.hasTargeters()) {
          if (inst instanceof OutlineableChunkEnd) {
            targetMap.put(ih, lastCopyHandle);
          } else {
            if (!chunkStartTargetMappingsPending) {
              chunkStartTargetMappingsPending = true;
              pendingTargetMappingHandle = ih;
            }
          }
        }
      } else {
        // Copy the instruction and append it to the outlined method's
        // InstructionList.
        Instruction c = inst.copy(); // Use clone for shallow copy

        if (c instanceof BranchInstruction) {
          lastCopyHandle = newIL.append((BranchInstruction) c);
        } else {
          lastCopyHandle = newIL.append(c);
        }

        if (c instanceof LocalVariableInstruction
            || c instanceof RET) {
          // For any instruction that touches a local variable,
          // check whether the local variable's value needs to be
          // copied into or out of the outlined method.  If so,
          // generate the code to perform the necessary copying, and
          // use localVarMap to map the variable in the original
          // method to the variable in the new method.
          IndexedInstruction lvi = (IndexedInstruction) c;
          int oldLocalVarIndex = lvi.getIndex();
          LocalVariableGen oldLVG =
              getLocalVariableRegistry()
                  .lookupRegisteredLocalVariable(oldLocalVarIndex,
                      ih.getPosition());
          LocalVariableGen newLVG =
              (LocalVariableGen) localVarMap.get(oldLVG);

          // Has the code already mapped this local variable to a
          // local in the new method?
          if (localVarMap.get(oldLVG) == null) {
            // Determine whether the local variable needs to be
            // copied into or out of the outlined by checking
            // whether the range of instructions in which the
            // variable is accessible is outside the range of
            // instructions in the outlineable chunk.
            // Special case a chunk start offset of zero:  a local
            // variable live at that position must be a method
            // parameter, so the code doesn't need to check whether
            // the variable is live before that point; being live
            // at offset zero is sufficient to know that the value
            // must be copied in to the outlined method.
            boolean copyInLocalValue =
                offsetInLocalVariableGenRange(oldLVG,
                    (outlineChunkStartOffset != 0)
                        ? outlineChunkStartOffset - 1
                        : 0);
            boolean copyOutLocalValue =
                offsetInLocalVariableGenRange(oldLVG,
                    outlineChunkEndOffset + 1);

            // For any variable that needs to be copied into or out
            // of the outlined method, create a field in the
            // CopyLocals class, and generate the necessary code for
            // copying the value.
            if (copyInLocalValue || copyOutLocalValue) {
              String varName = oldLVG.getName();
              Type varType = oldLVG.getType();
              newLVG = outlinedMethodGen.addLocalVariable(varName,
                  varType,
                  null,
                  null);
              int newLocalVarIndex = newLVG.getIndex();
              String varSignature = varType.getSignature();

              // Record the mapping from the old local to the new
              localVarMap.put(oldLVG, newLVG);

              copyAreaFieldCount++;
              String copyAreaFieldName =
                  "field" + copyAreaFieldCount;
              copyAreaCG.addField(
                  new Field(ACC_PUBLIC,
                      copyAreaCPG.addUtf8(copyAreaFieldName),
                      copyAreaCPG.addUtf8(varSignature),
                      null, copyAreaCPG.getConstantPool()));

              int fieldRef = cpg.addFieldref(argTypeName,
                  copyAreaFieldName,
                  varSignature);

              if (copyInLocalValue) {
                // Generate code for the old method to store the
                // value of the local into the correct field in
                // CopyLocals prior to invocation of the
                // outlined method.
                oldMethCopyInIL.append(
                    InstructionConstants.DUP);
                InstructionHandle copyInLoad =
                    oldMethCopyInIL.append(
                        loadLocal(oldLocalVarIndex, varType));
                oldMethCopyInIL.append(new PUTFIELD(fieldRef));

                // If the end of the live range of the old
                // variable was in the middle of the outlined
                // chunk.  Make the load of its value the new
                // end of its range.
                if (!copyOutLocalValue) {
                  revisedLocalVarEnd.put(oldLVG, copyInLoad);
                }

                // Generate code for start of the outlined
                // method to copy the value from a field in
                // CopyLocals to the new local in the outlined
                // method
                newMethCopyInIL.append(
                    InstructionConstants.ALOAD_1);
                newMethCopyInIL.append(new GETFIELD(fieldRef));
                newMethCopyInIL.append(
                    storeLocal(newLocalVarIndex, varType));
              }

              if (copyOutLocalValue) {
                // Generate code for the end of the outlined
                // method to copy the value from the new local
                // variable into a field in CopyLocals
                // method
                newMethCopyOutIL.append(
                    InstructionConstants.ALOAD_1);
                newMethCopyOutIL.append(
                    loadLocal(newLocalVarIndex, varType));
                newMethCopyOutIL.append(new PUTFIELD(fieldRef));

                // Generate code to copy the value from a field
                // in CopyLocals into a local in the original
                // method following invocation of the outlined
                // method.
                oldMethCopyOutIL.append(
                    InstructionConstants.DUP);
                oldMethCopyOutIL.append(new GETFIELD(fieldRef));
                InstructionHandle copyOutStore =
                    oldMethCopyOutIL.append(
                        storeLocal(oldLocalVarIndex, varType));

                // If the start of the live range of the old
                // variable was in the middle of the outlined
                // chunk.  Make this store into it the new start
                // of its range.
                if (!copyInLocalValue) {
                  revisedLocalVarStart.put(oldLVG,
                      copyOutStore);
                }
              }
            }
          }
        }

        if (ih.hasTargeters()) {
          targetMap.put(ih, lastCopyHandle);
        }

        // If this is the first instruction copied following a sequence
        // of OutlineableChunkStart instructions, indicate that the
        // sequence of old instruction all map to this newly created
        // instruction
        if (chunkStartTargetMappingsPending) {
          do {
            targetMap.put(pendingTargetMappingHandle,
                lastCopyHandle);
            pendingTargetMappingHandle =
                pendingTargetMappingHandle.getNext();
          } while (pendingTargetMappingHandle != ih);

          chunkStartTargetMappingsPending = false;
        }
      }
    }

    // Pass 2: Walk old and new instruction lists, updating branch targets
    // and local variable references in the new list
    InstructionHandle ih = first;
    InstructionHandle ch = newIL.getStart();

    while (ch != null) {
      // i == old instruction; c == copied instruction
      Instruction i = ih.getInstruction();
      Instruction c = ch.getInstruction();

      if (i instanceof BranchInstruction) {
        BranchInstruction bc = (BranchInstruction) c;
        BranchInstruction bi = (BranchInstruction) i;
        InstructionHandle itarget = bi.getTarget(); // old target

        // New target must be in targetMap
        InstructionHandle newTarget =
            (InstructionHandle) targetMap.get(itarget);

        bc.setTarget(newTarget);

        // Handle LOOKUPSWITCH or TABLESWITCH which may have many
        // target instructions
        if (bi instanceof Select) {
          InstructionHandle[] itargets = ((Select) bi).getTargets();
          InstructionHandle[] ctargets = ((Select) bc).getTargets();

          // Update all targets
          for (int j = 0; j < itargets.length; j++) {
            ctargets[j] =
                (InstructionHandle) targetMap.get(itargets[j]);
          }
        }
      } else if (i instanceof LocalVariableInstruction
          || i instanceof RET) {
        // For any instruction that touches a local variable,
        // map the location of the variable in the original
        // method to its location in the new method.
        IndexedInstruction lvi = (IndexedInstruction) c;
        int oldLocalVarIndex = lvi.getIndex();
        LocalVariableGen oldLVG =
            getLocalVariableRegistry()
                .lookupRegisteredLocalVariable(oldLocalVarIndex,
                    ih.getPosition());
        LocalVariableGen newLVG =
            (LocalVariableGen) localVarMap.get(oldLVG);
        int newLocalVarIndex;

        if (newLVG == null) {
          // Create new variable based on old variable - use same
          // name and type, but we will let the variable be active
          // for the entire outlined method.
          // LocalVariableGen oldLocal = oldLocals[oldLocalVarIndex];
          String varName = oldLVG.getName();
          Type varType = oldLVG.getType();
          newLVG = outlinedMethodGen.addLocalVariable(varName,
              varType,
              null,
              null);
          newLocalVarIndex = newLVG.getIndex();
          localVarMap.put(oldLVG, newLVG);

          // The old variable's live range was wholly contained in
          // the outlined chunk.  There should no longer be stores
          // of values into it or loads of its value, so we can just
          // mark its live range as the reference to the outlined
          // method.
          revisedLocalVarStart.put(oldLVG, outlinedMethodRef);
          revisedLocalVarEnd.put(oldLVG, outlinedMethodRef);
        } else {
          newLocalVarIndex = newLVG.getIndex();
        }
        lvi.setIndex(newLocalVarIndex);
      }

      // If the old instruction marks the end of the range of a local
      // variable, make sure that any slots on the stack reserved for
      // local variables are made available for reuse by calling
      // MethodGenerator.removeLocalVariable
      if (ih.hasTargeters()) {
        InstructionTargeter[] targeters = ih.getTargeters();

        for (int idx = 0; idx < targeters.length; idx++) {
          InstructionTargeter targeter = targeters[idx];

          if (targeter instanceof LocalVariableGen
              && ((LocalVariableGen) targeter).getEnd() == ih) {
            Object newLVG = localVarMap.get(targeter);
            if (newLVG != null) {
              outlinedMethodGen.removeLocalVariable(
                  (LocalVariableGen) newLVG);
            }
          }
        }
      }

      // If the current instruction in the original list was a marker,
      // it wasn't copied, so don't advance through the list of copied
      // instructions yet.
      if (!(i instanceof MarkerInstruction)) {
        ch = ch.getNext();
      }
      ih = ih.getNext();

    }

    // POP the reference to the CopyLocals object from the stack
    oldMethCopyOutIL.append(InstructionConstants.POP);

    // Now that the generation of the outlined code is complete, update
    // the old local variables with new start and end ranges, as required.
    Iterator revisedLocalVarStartPairIter = revisedLocalVarStart.entrySet()
        .iterator();
    while (revisedLocalVarStartPairIter.hasNext()) {
      Map.Entry lvgRangeStartPair =
          (Map.Entry) revisedLocalVarStartPairIter.next();
      LocalVariableGen lvg = (LocalVariableGen) lvgRangeStartPair.getKey();
      InstructionHandle startInst =
          (InstructionHandle) lvgRangeStartPair.getValue();

      lvg.setStart(startInst);

    }

    Iterator revisedLocalVarEndPairIter = revisedLocalVarEnd.entrySet()
        .iterator();
    while (revisedLocalVarEndPairIter.hasNext()) {
      Map.Entry lvgRangeEndPair =
          (Map.Entry) revisedLocalVarEndPairIter.next();
      LocalVariableGen lvg = (LocalVariableGen) lvgRangeEndPair.getKey();
      InstructionHandle endInst =
          (InstructionHandle) lvgRangeEndPair.getValue();

      lvg.setEnd(endInst);
    }

    xsltc.dumpClass(copyAreaCG.getJavaClass());

    // Assemble the instruction lists so that the old method invokes the
    // new outlined method
    InstructionList oldMethodIL = getInstructionList();

    oldMethodIL.insert(first, oldMethCopyInIL);
    oldMethodIL.insert(first, oldMethCopyOutIL);

    // Insert the copying code into the outlined method
    newIL.insert(newMethCopyInIL);
    newIL.append(newMethCopyOutIL);
    newIL.append(InstructionConstants.RETURN);

    // Discard instructions in outlineable chunk from old method
    try {
      oldMethodIL.delete(first, last);
    } catch (TargetLostException e) {
      InstructionHandle[] targets = e.getTargets();
      // If there were still references to old instructions lingering,
      // clean those up.  The only instructions targetting the deleted
      // instructions should have been part of the chunk that was just
      // deleted, except that instructions might branch to the start of
      // the outlined chunk; similarly, all the live ranges of local
      // variables should have been adjusted, except for unreferenced
      // variables.
      for (int i = 0; i < targets.length; i++) {
        InstructionHandle lostTarget = targets[i];
        InstructionTargeter[] targeters = lostTarget.getTargeters();
        for (int j = 0; j < targeters.length; j++) {
          if (targeters[j] instanceof LocalVariableGen) {
            LocalVariableGen lvgTargeter =
                (LocalVariableGen) targeters[j];
            // In the case of any lingering variable references,
            // just make the live range point to the outlined
            // function reference.  Such variables should be unused
            // anyway.
            if (lvgTargeter.getStart() == lostTarget) {
              lvgTargeter.setStart(outlinedMethodRef);
            }
            if (lvgTargeter.getEnd() == lostTarget) {
              lvgTargeter.setEnd(outlinedMethodRef);
            }
          } else {
            targeters[j].updateTarget(lostTarget,
                outlinedMethodCallSetup);
          }
        }
      }
    }

    // Make a copy for the new method of all exceptions that might be thrown
    String[] exceptions = getExceptions();
    for (int i = 0; i < exceptions.length; i++) {
      outlinedMethodGen.addException(exceptions[i]);
    }

    return outlinedMethodGen.getThisMethod();
  }

  /**
   * Helper method to generate an instance of a subclass of
   * {@link LoadInstruction} based on the specified {@link Type} that will
   * load the specified local variable
   *
   * @param index the JVM stack frame index of the variable that is to be loaded
   * @param type the {@link Type} of the variable
   * @return the generated {@link LoadInstruction}
   */
  private static Instruction loadLocal(int index, Type type) {
    if (type == Type.BOOLEAN) {
      return new ILOAD(index);
    } else if (type == Type.INT) {
      return new ILOAD(index);
    } else if (type == Type.SHORT) {
      return new ILOAD(index);
    } else if (type == Type.LONG) {
      return new LLOAD(index);
    } else if (type == Type.BYTE) {
      return new ILOAD(index);
    } else if (type == Type.CHAR) {
      return new ILOAD(index);
    } else if (type == Type.FLOAT) {
      return new FLOAD(index);
    } else if (type == Type.DOUBLE) {
      return new DLOAD(index);
    } else {
      return new ALOAD(index);
    }
  }

  /**
   * Helper method to generate an instance of a subclass of
   * {@link StoreInstruction} based on the specified {@link Type} that will
   * store a value in the specified local variable
   *
   * @param index the JVM stack frame index of the variable that is to be stored
   * @param type the {@link Type} of the variable
   * @return the generated {@link StoredInstruction}
   */
  private static Instruction storeLocal(int index, Type type) {
    if (type == Type.BOOLEAN) {
      return new ISTORE(index);
    } else if (type == Type.INT) {
      return new ISTORE(index);
    } else if (type == Type.SHORT) {
      return new ISTORE(index);
    } else if (type == Type.LONG) {
      return new LSTORE(index);
    } else if (type == Type.BYTE) {
      return new ISTORE(index);
    } else if (type == Type.CHAR) {
      return new ISTORE(index);
    } else if (type == Type.FLOAT) {
      return new FSTORE(index);
    } else if (type == Type.DOUBLE) {
      return new DSTORE(index);
    } else {
      return new ASTORE(index);
    }
  }

  /**
   * Track the number of outlineable chunks seen.
   */
  private int m_totalChunks = 0;

  /**
   * Track the number of outlineable chunks started but not yet ended.  Used
   * to detect imbalances in byte code generation.
   */
  private int m_openChunks = 0;

  /**
   * Mark the end of the method's
   * {@link InstructionList} as the start of an outlineable chunk of code.
   * The outlineable chunk begins after the {@link InstructionHandle} that is
   * at the end of the method's {@link InstructionList}, or at the start of
   * the method if the <code>InstructionList</code> is empty.
   * See {@link OutlineableChunkStart} for more information.
   */
  public void markChunkStart() {
    // m_chunkTree.markChunkStart();
    getInstructionList()
        .append(OutlineableChunkStart.OUTLINEABLECHUNKSTART);
    m_totalChunks++;
    m_openChunks++;
  }

  /**
   * Mark the end of an outlineable chunk of code.  See
   * {@link OutlineableChunkStart} for more information.
   */
  public void markChunkEnd() {
    // m_chunkTree.markChunkEnd();
    getInstructionList()
        .append(OutlineableChunkEnd.OUTLINEABLECHUNKEND);
    m_openChunks--;
    if (m_openChunks < 0) {
      String msg = (new ErrorMsg(ErrorMsg.OUTLINE_ERR_UNBALANCED_MARKERS))
          .toString();
      throw new InternalError(msg);
    }
  }

  /**
   * <p>Get all {@link Method}s generated by this {@link MethodGenerator}.
   * The {@link MethodGen#getMethod()} only returns a single
   * <code>Method</code> object.  This method takes into account the Java
   * Virtual Machine Specification limit of 64KB on the size of a method, and
   * may return more than one <code>Method</code>.</p>
   * <p>If the code associated with the <code>MethodGenerator</code> would
   * exceed the 64KB limit, this method will attempt to split the code in
   * the {@link InstructionList} associated with this
   * <code>MethodGenerator</code> into several methods.</p>
   *
   * @param classGen the {@link ClassGenerator} of which these methods are members
   * @return an array of all the <code>Method</code>s generated
   */
  Method[] getGeneratedMethods(ClassGenerator classGen) {
    Method[] generatedMethods;
    InstructionList il = getInstructionList();
    InstructionHandle last = il.getEnd();

    il.setPositions();

    int instructionListSize =
        last.getPosition() + last.getInstruction().getLength();

    // Need to look for any branch target offsets that exceed the range
    // [-32768,32767]
    if (instructionListSize > MAX_BRANCH_TARGET_OFFSET) {
      boolean ilChanged = widenConditionalBranchTargetOffsets();

      // If any branch instructions needed widening, recompute the size
      // of the byte code for the method
      if (ilChanged) {
        il.setPositions();
        last = il.getEnd();
        instructionListSize =
            last.getPosition() + last.getInstruction().getLength();
      }
    }

    if (instructionListSize > MAX_METHOD_SIZE) {
      generatedMethods = outlineChunks(classGen, instructionListSize);
    } else {
      generatedMethods = new Method[]{getThisMethod()};
    }
    return generatedMethods;
  }

  protected Method getThisMethod() {
    stripAttributes(true);
    setMaxLocals();
    setMaxStack();
    removeNOPs();

    return getMethod();
  }

  /**
   * <p>Rewrites branches to avoid the JVM limits of relative branch
   * offsets.  There is no need to invoke this method if the bytecode for the
   * {@link MethodGenerator} does not exceed 32KB.</p>
   * <p>The Java Virtual Machine Specification permits the code portion of a
   * method to be up to 64KB in length.  However, some control transfer
   * instructions specify relative offsets as a signed 16-bit quantity,
   * limiting the range to a subset of the instructions that might be in a
   * method.</p>
   * <p>The <code>TABLESWITCH</code> and <code>LOOKUPSWITCH</code>
   * instructions always use 32-bit signed relative offsets, so they are
   * immune to this problem.</p>
   * <p>The <code>GOTO</code> and <code>JSR</code>
   * instructions come in two forms, one of which uses 16-bit relative
   * offsets, and the other of which uses 32-bit relative offsets.  The BCEL
   * library decides whether to use the wide form of <code>GOTO</code> or
   * <code>JSR</code>instructions based on the relative offset of the target
   * of the instruction without any intervention by the user of the
   * library.</p>
   * <p>This leaves the various conditional branch instructions,
   * <code>IFEQ</code>, <code>IFNULL</code>, <code>IF_ICMPEQ</code>,
   * <em>et al.</em>, all of which use 16-bit signed relative offsets, with no
   * 32-bit wide form available.</p>
   * <p>This method scans the {@link InstructionList} associated with this
   * {@link MethodGenerator} and finds all conditional branch instructions
   * that might exceed the 16-bit limitation for relative branch offsets.
   * The logic of each such instruction is inverted, and made to target the
   * instruction which follows it.  An unconditional branch to the original
   * target of the instruction is then inserted between the conditional
   * branch and the instruction which previously followed it.  The
   * unconditional branch is permitted to have a 16-bit or a 32-bit relative
   * offset, as described above.  For example,
   * <code>
   * 1234:   NOP
   * ...
   * 55278:  IFEQ -54044
   * 55280:  NOP
   * </code>
   * is rewritten as
   * <code>
   * 1234:   NOP
   * ...
   * 55278:  IFNE 7
   * 55280:  GOTO_W -54046
   * 55285:  NOP
   * </code></p>
   * <p><b>Preconditions:</b>
   * <ul><li>The {@link InstructionList#setPositions()} has been called for
   * the <code>InstructionList</code> associated with this
   * <code>MethodGenerator</code>.
   * </li></ul></p>
   * <p><b>Postconditions:</b>
   * <ul><li>Any further changes to the <code>InstructionList</code> for this
   * <code>MethodGenerator</code> will invalidate the changes made by this
   * method.</li></ul>
   * </p>
   *
   * @return <code>true</code> if the <code>InstructionList</code> was modified; <code>false</code>
   * otherwise
   * @see The Java Virtual Machine Specification, Second Edition
   */
  boolean widenConditionalBranchTargetOffsets() {
    boolean ilChanged = false;
    int maxOffsetChange = 0;
    InstructionList il = getInstructionList();

    // Loop through all the instructions, finding those that would be
    // affected by inserting new instructions in the InstructionList, and
    // calculating the maximum amount by which the relative offset between
    // two instructions could possibly change.
    // In part this loop duplicates code in
    // org.apache.bcel.generic.InstructionList.setPosition(), which does
    // this to determine whether to use 16-bit or 32-bit offsets for GOTO
    // and JSR instructions.  Ideally, that method would do the same for
    // conditional branch instructions, but it doesn't, so we duplicate the
    // processing here.
    for (InstructionHandle ih = il.getStart();
        ih != null;
        ih = ih.getNext()) {
      Instruction inst = ih.getInstruction();

      switch (inst.getOpcode()) {
        // Instructions that may have 16-bit or 32-bit branch targets.
        // The size of the branch offset might increase by two bytes.
        case Constants.GOTO:
        case Constants.JSR:
          maxOffsetChange = maxOffsetChange + 2;
          break;
        // Instructions that contain padding for alignment purposes
        // Up to three bytes of padding might be needed.  For greater
        // accuracy, we should be able to discount any padding already
        // added to these instructions by InstructionList.setPosition(),
        // their APIs do not expose that information.
        case Constants.TABLESWITCH:
        case Constants.LOOKUPSWITCH:
          maxOffsetChange = maxOffsetChange + 3;
          break;
        // Instructions that might be rewritten by this method as a
        // conditional branch followed by an unconditional branch.
        // The unconditional branch would require five bytes.
        case Constants.IF_ACMPEQ:
        case Constants.IF_ACMPNE:
        case Constants.IF_ICMPEQ:
        case Constants.IF_ICMPGE:
        case Constants.IF_ICMPGT:
        case Constants.IF_ICMPLE:
        case Constants.IF_ICMPLT:
        case Constants.IF_ICMPNE:
        case Constants.IFEQ:
        case Constants.IFGE:
        case Constants.IFGT:
        case Constants.IFLE:
        case Constants.IFLT:
        case Constants.IFNE:
        case Constants.IFNONNULL:
        case Constants.IFNULL:
          maxOffsetChange = maxOffsetChange + 5;
          break;
      }
    }

    // Now that the maximum number of bytes by which the method might grow
    // has been determined, look for conditional branches to see which
    // might possibly exceed the 16-bit relative offset.
    for (InstructionHandle ih = il.getStart();
        ih != null;
        ih = ih.getNext()) {
      Instruction inst = ih.getInstruction();

      if (inst instanceof IfInstruction) {
        IfInstruction oldIfInst = (IfInstruction) inst;
        BranchHandle oldIfHandle = (BranchHandle) ih;
        InstructionHandle target = oldIfInst.getTarget();
        int relativeTargetOffset = target.getPosition()
            - oldIfHandle.getPosition();

        // Consider the worst case scenario in which the conditional
        // branch and its target are separated by all the instructions
        // in the method that might increase in size.  If that results
        // in a relative offset that cannot be represented as a 32-bit
        // signed quantity, rewrite the instruction as described above.
        if ((relativeTargetOffset - maxOffsetChange
            < MIN_BRANCH_TARGET_OFFSET)
            || (relativeTargetOffset + maxOffsetChange
            > MAX_BRANCH_TARGET_OFFSET)) {
          // Invert the logic of the IF instruction, and append
          // that to the InstructionList following the original IF
          // instruction
          InstructionHandle nextHandle = oldIfHandle.getNext();
          IfInstruction invertedIfInst = oldIfInst.negate();
          BranchHandle invertedIfHandle = il.append(oldIfHandle,
              invertedIfInst);

          // Append an unconditional branch to the target of the
          // original IF instruction after the new IF instruction
          BranchHandle gotoHandle = il.append(invertedIfHandle,
              new GOTO(target));

          // If the original IF was the last instruction in
          // InstructionList, add a new no-op to act as the target
          // of the new IF
          if (nextHandle == null) {
            nextHandle = il.append(gotoHandle, NOP);
          }

          // Make the new IF instruction branch around the GOTO
          invertedIfHandle.updateTarget(target, nextHandle);

          // If anything still "points" to the old IF instruction,
          // make adjustments to refer to either the new IF or GOTO
          // instruction
          if (oldIfHandle.hasTargeters()) {
            InstructionTargeter[] targeters =
                oldIfHandle.getTargeters();

            for (int i = 0; i < targeters.length; i++) {
              InstructionTargeter targeter = targeters[i];
              // Ideally, one should simply be able to use
              // InstructionTargeter.updateTarget to change
              // references to the old IF instruction to the new
              // IF instruction.  However, if a LocalVariableGen
              // indicated the old IF marked the end of the range
              // in which the IF variable is in use, the live
              // range of the variable must extend to include the
              // newly created GOTO instruction.  The need for
              // this sort of specific knowledge of an
              // implementor of the InstructionTargeter interface
              // makes the code more fragile.  Future implementors
              // of the interface might have similar requirements
              // which wouldn't be accommodated seemlessly.
              if (targeter instanceof LocalVariableGen) {
                LocalVariableGen lvg =
                    (LocalVariableGen) targeter;
                if (lvg.getStart() == oldIfHandle) {
                  lvg.setStart(invertedIfHandle);
                } else if (lvg.getEnd() == oldIfHandle) {
                  lvg.setEnd(gotoHandle);
                }
              } else {
                targeter.updateTarget(oldIfHandle,
                    invertedIfHandle);
              }
            }
          }

          try {
            il.delete(oldIfHandle);
          } catch (TargetLostException tle) {
            // This can never happen - we updated the list of
            // instructions that target the deleted instruction
            // prior to deleting it.
            String msg =
                new ErrorMsg(ErrorMsg.OUTLINE_ERR_DELETED_TARGET,
                    tle.getMessage()).toString();
            throw new InternalError(msg);
          }

          // Adjust the pointer in the InstructionList to point after
          // the newly inserted IF instruction
          ih = gotoHandle;

          // Indicate that this method rewrote at least one IF
          ilChanged = true;
        }
      }
    }

    // Did this method rewrite any IF instructions?
    return ilChanged;
  }
}
